package com.bingguo.filter;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.bingguo.config.JwtProperties;
import com.bingguo.config.UrlProperties;
import com.bingguo.constant.TokenConstants;
import com.bingguo.util.JwtUtil;
import com.bingguo.util.RedisUtil;
import com.bingguo.util.ServletUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.ArrayList;

@Component
@Slf4j
@Order(value = 0)
public class GatewayFilter implements GlobalFilter {

    @Autowired
    protected UrlProperties urlProperties;
    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private JwtProperties jwtProperties;
    @Resource
    private AntPathMatcher antPathMatcher;//路径匹配器

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String url = exchange.getRequest().getPath().value();//请求路径

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest.Builder mutate = request.mutate();

        // 跳过不需要验证的路径
        if (urlProperties.matchWhiteUrlsList(url)) {
            log.debug("请求地址【{}】在白名单不需要验证", exchange.getRequest().getPath());
            return chain.filter(exchange);
        }
        // 拒绝不能访问的路径
        if (urlProperties.matchBlackList(url)) {
            log.debug("请求地址【{}】在黑名单不允许访问", exchange.getRequest().getPath());
            return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问");
        }
        //获取token
        String token = getToken(request);
        if (StringUtils.isEmpty(token)) {
            return unauthorizedResponse(exchange, "令牌不能为空");
        }
        Claims claims = jwtUtil.parseToken(token);
        if (claims == null) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        // 校验token
        String uuid = jwtUtil.getUuid(claims);
        String tokenKey = getTokenKey(uuid);
        boolean isLogin = redisUtil.hasKey(tokenKey);
        if (!isLogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }
        // 延期key失效时间
        redisUtil.expire(tokenKey, jwtProperties.getExpire());

        //TODO 鉴权

        //如果有启用security，登录成功必须设置上下文
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("", null, new ArrayList<>());
        return chain.filter(exchange.mutate().request(mutate.build()).build()).
                subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authenticationToken))
                .doFinally(signal -> ReactiveSecurityContextHolder.clearContext());
    }



    /**
     * 获取请求token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(TokenConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
            token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);
        }
        return token;
    }

    /**
     * 生成未认证响应
     * @param exchange
     * @param msg
     * @return
     */
    protected Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("请求地址【{}】认证失败", exchange.getRequest().getPath());
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED.value());
    }
    /**
     * 获取缓存key
     */
    private String getTokenKey(String uuid) {
        return TokenConstants.LOGIN_TOKEN_KEY + uuid;
    }
}
